﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Specialized;
using System.Net.Cache;
using System.Web;
using System.IO.Compression;

namespace Transcend.DP3.Http
{

	public class WebRequestHelper
	{

		protected static log4net.ILog Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

		#region 常量

		/// <summary>
		/// 文件域开始标志
		/// </summary>
		private const string post_filefield_begin = "\r\n--{0}\r\n";

		/// <summary>
		/// 文件域头信息
		/// </summary>
		private const string post_filefield_header = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";

		/// <summary>
		/// 数据域格式
		/// </summary>
		private const string post_datafield = "\r\n--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}";

		/// <summary>
		/// 数据域结尾
		/// </summary>
		private const string post_datafield_end = "\r\n--{0}--\r\n";

		/// <summary>
		/// POST请求数据编码方式，没有上传文件时使用
		/// </summary>
		private const string post_urlencoded = "application/x-www-form-urlencoded";

		/// <summary>
		/// POST请求数据编码方式，有上传文件时使用
		/// </summary>
		private const string post_multipart = "multipart/form-data;boundary={0}";

		#endregion

		#region 属性

		private string dataBounary = "---------------" + DateTime.Now.Ticks.ToString("x");
		/// <summary>
		/// Http数据分割符
		/// </summary>
		public string DataBounary
		{
			get { return dataBounary; }
			set { dataBounary = value; }
		}

		#endregion

		public WebRequestHelper()
		{ }

		/// <summary>
		/// 将上传文件写入输出流中，必须是post且content-type为multipart/form-data
		/// </summary>
		/// <param name="st">输出流</param>
		/// <param name="name">域名称</param>
		/// <param name="path">文件路径</param>
		protected void WriteFile(MemoryStream st, string name, string path, WebRequestConfigurator conf)
		{

			if (!File.Exists(path)) throw new ArgumentException(string.Format("上传的文件[{0}]已不存在", path));
			//文件开始标志
			byte[] fileStart = conf.RequestEncoding.GetBytes(string.Format(post_filefield_begin, dataBounary));
			//文件信息
			byte[] fileHeader = conf.RequestEncoding.GetBytes(string.Format(post_filefield_header, name, Path.GetFileName(path)));
			//将文件开始标志写入流
			st.Write(fileStart, 0, fileStart.Length);
			//将文件信息写入流
			st.Write(fileHeader, 0, fileHeader.Length);

			byte[] buf = new byte[conf.SndBufferSize];
			int read = 0;

			//将文件内容写入流
			using (FileStream fStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				while ((read = fStream.Read(buf, 0, conf.SndBufferSize)) != 0)
				{
					st.Write(buf, 0, read);
				}
			}
		}

		/// <summary>
		/// 创建HttpWebRequest实例
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		protected HttpWebRequest PrepareRequest(string url, WebRequestConfigurator conf)
		{
			HttpWebRequest r = (HttpWebRequest)HttpWebRequest.Create(url);
			// 为了方便调试，将头信息一个一个添加
			if (conf.Headers.Count > 0)
			{
				foreach (string hd in conf.Headers)
				{
					r.Headers.Add(hd, conf.Headers[hd]);
				}
			}

			r.ReadWriteTimeout = conf.ReadWriteTimeout;
			r.Timeout = conf.RequestTimeout;
			r.ProtocolVersion = conf.ProtocolVersion;
			r.Accept = conf.Accept;
			r.UserAgent = conf.UserAgent;
			r.Referer = conf.Referer;
			r.KeepAlive = conf.KeepAlive;
			r.Expect = conf.Expect;
			r.CachePolicy = conf.RequestCachePolicy;
			r.ServicePoint.Expect100Continue = conf.EnableExpect100Continue;
			r.ServicePoint.ReceiveBufferSize = conf.RcvBufferSize;
			r.CookieContainer = conf.CkContainer;

			return r;
		}

		/// <summary>
		/// 发起Http Get请求
		/// </summary>
		/// <param name="url">url地址</param>
		/// <param name="conf"></param>
		/// <returns></returns>
		public HttpWebResponse HttpGet(string url, WebRequestConfigurator conf)
		{

			NameValueCollection allParam = new NameValueCollection();
			if (conf.DataFields.Count > 0) allParam.Add(conf.DataFields);

			Uri uri = new Uri(url);
			string path = uri.GetLeftPart(UriPartial.Path);
			string qString = string.Empty;
			if(url.Length > path.Length)
			qString = url.Substring(path.Length);
			if (!string.IsNullOrEmpty(qString))
			{
				NameValueCollection qParams = HttpUtility.ParseQueryString(qString);
				allParam.Add(qParams);
			}

			string val = string.Empty;
			qString = string.Empty;
			foreach (string key in allParam)
			{
				val = allParam[key];
				if (string.IsNullOrEmpty(val)) val = string.Empty;
				val = Uri.UnescapeDataString(val);
				qString += string.Format("&{0}={1}", key, HttpUtility.UrlEncode(val));
			}

			if (!string.IsNullOrEmpty(qString)) url = path + "?" + qString.Substring(1);
			
			Logger.Debug(string.Format("发起Get请求:{0}", url));

			HttpWebRequest r = PrepareRequest(url, conf);
			r.Method = "GET";

			return r.GetResponse() as HttpWebResponse;
		}

		/// <summary>
		/// 发起Http Post请求
		/// </summary>
		/// <param name="url"></param>
		/// <param name="conf"></param>
		/// <returns></returns>
		public HttpWebResponse HttpPost(string url, WebRequestConfigurator conf)
		{
			Logger.Debug(string.Format("发起POST请求:{0}", url));
			HttpWebRequest r = PrepareRequest(url, conf);
			r.Method = "POST";
			using (MemoryStream st = new MemoryStream())
			{
				string val = string.Empty;
				byte[] buf = null;
				int i = 0;

				// 通过检查是否存在文件域，如果存在文件域则要使用multipart/form-data
				if (conf.FileFields.Count == 0)
				{
					r.ContentType = post_urlencoded;
					ASCIIEncoding asciEnc = new ASCIIEncoding();
					foreach (string key in conf.DataFields.Keys)
					{
						val = conf.DataFields[key];
						if (string.IsNullOrEmpty(val)) val = string.Empty;
						buf = conf.RequestEncoding.GetBytes(string.Format("{0}={1}", key, HttpUtility.UrlEncode(val)));
						if (++i > 1) st.Write(asciEnc.GetBytes("&"), 0, 1);
						st.Write(buf, 0, buf.Length);
					}
				}
				else
				{
					r.ContentType = string.Format(post_multipart, dataBounary);
					foreach (string key in conf.DataFields.Keys)
					{
						val = conf.DataFields[key];
						if (string.IsNullOrEmpty(val)) val = string.Empty;
						buf = conf.RequestEncoding.GetBytes(String.Format(post_datafield, dataBounary, key, val));
						st.Write(buf, 0, buf.Length);
					}

					foreach (string key in conf.FileFields.Keys)
					{
						val = conf.FileFields[key];
						WriteFile(st, key, val, conf);
					}
					buf = Encoding.ASCII.GetBytes(string.Format(post_datafield_end, dataBounary));
					st.Write(buf, 0, buf.Length);
				}

				r.ContentLength = st.Length;

				using (Stream outSt = r.GetRequestStream())
				{
					st.WriteTo(outSt);
				}

			}

			return r.GetResponse() as HttpWebResponse;
		}

		/// <summary>
		/// 发起http请求，方式有method指定
		/// </summary>
		/// <param name="method">支持的方式为:get,post</param>
		/// <param name="url"></param>
		/// <param name="conf"></param>
		/// <returns></returns>
		public HttpWebResponse HttpRequest(string method, string url, WebRequestConfigurator conf)
		{
			if (string.IsNullOrEmpty(method)) throw new ArgumentException("请求方式参数method为空");
			method = method.ToLower();
			switch (method)
			{
				case "get":
					return HttpGet(url, conf);
				case "post":
					return HttpPost(url, conf);
				default:
					throw new ArgumentException(string.Format("请求方式{0}不受支持", method));
			}
		}

		/// <summary>
		/// 发起Http请求，返回输出流；该方法会通过服务器返回的content-encoding头信息自动解码gzip,deflate等压缩算法压缩的内容
		/// </summary>
		/// <param name="url"></param>
		/// <param name="conf"></param>
		public void RequestStream(string method, string url, WebRequestConfigurator conf, Stream outSt)
		{
			using (HttpWebResponse r = HttpRequest(method, url, conf))
			{
				Stream inSt = r.GetResponseStream();
				string contentEncoding = r.ContentEncoding;
				if (string.IsNullOrEmpty(contentEncoding)) contentEncoding = "default";
				contentEncoding = contentEncoding.ToLower();

				switch (contentEncoding)
				{
					case "gzip":
						inSt = new GZipStream(inSt, CompressionMode.Decompress);
						break;
					case "deflate":
						inSt = new DeflateStream(inSt, CompressionMode.Decompress);
						break;
				}
				
				byte [] buf = new byte[conf.RcvBufferSize];
				int readed = 0;
				while((readed = inSt.Read(buf, 0, conf.RcvBufferSize)) > 0)
				{
					outSt.Write(buf, 0, readed);
				}
			}
		}

		/// <summary>
		/// 发起Http请求，返回字符串
		/// </summary>
		/// <param name="url"></param>
		/// <param name="conf"></param>
		public string RequestString(string method, string url, WebRequestConfigurator conf)
		{
			string rText = string.Empty;
			using (MemoryStream st = new MemoryStream())
			{
				RequestStream(method, url, conf, st);
				st.Position = 0;
				using (StreamReader sr = new StreamReader(st))
				{
					rText = sr.ReadToEnd();
				}
			}
			return rText;
		}

	}

}